﻿using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Ast;
using System.Collections.Generic;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Scripting.Utils;
using System.Threading;

namespace Cjc.Calculation.ExpressionEngine
{
	public class ParameterizedInvoker : Invoker
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="code">The code.</param>
		/// <param name="language">The language.</param>
		public ParameterizedInvoker( Expression code, LanguageContext language, params ParameterExpression[] parameters )
			: base( code, language )
		{
			this.Parameters = parameters;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="code">The code.</param>
		/// <param name="language">The language.</param>
		public ParameterizedInvoker( LambdaExpression code, LanguageContext language )
			: this( code.Body, language, code.Parameters.ToArray() )
		{
		}

		/// <summary>
		/// Gets or sets the parameters.
		/// </summary>
		/// <value>The parameters.</value>
		public IEnumerable<ParameterExpression> Parameters { get; set; }

		/// <summary>
		/// Makes the delegate.
		/// </summary>
		/// <returns></returns>
		protected override Delegate MakeDelegate()
		{
			var lambda = ( Code is LambdaExpression )
				? (LambdaExpression)Code
				: DlrParameterResolver.MakeLambda( Code, "Invoker", Parameters.ToArray() );

			return lambda.Compile();
		}

		/// <summary>
		/// Invokes the target with the specified scope.
		/// </summary>
		/// <param name="scope">The scope.</param>
		/// <param name="args">Optional arguments.</param>
		/// <returns>The result of the invocation.</returns>
		public object Invoke( Scope scope, params object[] args )
		{
			return Target.DynamicInvoke( args );
		}
	}

	public class Invoker
	{
        public delegate object CallTarget(ScopeAndLanguage scopelan);

		private static DlrParameterResolver resolver = new DlrParameterResolver();

		protected LanguageContext language;
		private Expression code;
		private Delegate target;

		/// <summary>
		/// Initializes a new instance of the <see cref="Invoker&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="code">The code.</param>
		/// <param name="language">The language.</param>
		public Invoker( Expression code, LanguageContext language )
		{
			this.code = code;
			this.language = language;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Invoker"/> class.
		/// </summary>
		/// <param name="invoker">The invoker.</param>
		/// <param name="arguments">The arguments.</param>
		/// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
		public Invoker( Invoker invoker, params object[] arguments )
			: this( invoker, (IEnumerable<object>)arguments )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Invoker"/> class.
		/// </summary>
		/// <param name="invoker">The invoker.</param>
		/// <param name="arguments">The arguments.</param>
		/// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
		public Invoker( Invoker invoker, IEnumerable<object> arguments )
			: this( invoker.Target, invoker.language, arguments )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Invoker"/> class.
		/// </summary>
		/// <param name="target">The target delegate.</param>
		/// <param name="language">The language context.</param>
		/// <param name="arguments">The arguments.</param>
		/// <remarks>This constructor "bakes" arguments into the invocation of another <see cref="Invoker"/></remarks>
		public Invoker( Delegate target, LanguageContext language, IEnumerable<object> arguments )
		{
			if ( target != null )
			{
				var args = new Expression[]
					{
						DlrParameterResolver.ScopeAndLanguageParameter
					}.Union( arguments.Select(
						a => ( a is Expression )
							? (Expression)a
							: Expression.Constant( a, typeof( object ) ) ) ).ToArray();

				this.code = Expression.Invoke(
					Expression.Constant( target ),
					args );

				this.language = language;
			}
		}

		/// <summary>
		/// Gets or sets the code.
		/// </summary>
		/// <value>The code.</value>
		public Expression Code
		{
			get { return code; }
			set
			{
				Interlocked.Exchange<Expression>( ref code, value );
				Interlocked.Exchange<Delegate>( ref target, null );
			}
		}

		/// <summary>
		/// Gets the target.
		/// </summary>
		/// <value>The target.</value>
		public Delegate Target
		{
			get
			{
				if ( target == null && code != null )
				{
					Interlocked.CompareExchange<Delegate>( ref target, MakeDelegate(), null );
				}

				return target;
			}
		}

		/// <summary>
		/// Makes the delegate.
		/// </summary>
		/// <returns></returns>
		protected virtual Delegate MakeDelegate()
		{
			return DlrParameterResolver.MakeLambda<CallTarget>( code, "Invoker", null ).Compile();
		}

		/// <summary>
		/// Invokes the target with the specified scope.
		/// </summary>
		/// <param name="scope">The scope.</param>
		/// <returns>The result of the invocation.</returns>
		public virtual object Invoke( WorksheetScope scope )
		{
			/* Playing around with SaveToAssembly...  maybe useful if figure out serialization issue
			 * (final code wouldn't be here though)...

			var sourceUnit = new Microsoft.Scripting.SourceUnit(
				language,
				NullTextContentProvider.Null,
				null,
				Microsoft.Scripting.SourceCodeKind.Expression );

			var scriptCode = new OptimizedScriptCode( DlrParameterResolver.MakeLambda<CallTarget>( code, "Invoker", null ), sourceUnit );

			Microsoft.Scripting.ScriptCode.SaveToAssembly( @"e:\scribble\testDLR.dll", scriptCode );*/

            return ((CallTarget)Target)(new ScopeAndLanguage { Scope = scope, LanguageContext = language });
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			return Code.ToString();
		}
	}
}